package lip6.ivi;

import gurobi.GRB;
import gurobi.GRBEnv;
import gurobi.GRBException;
import gurobi.GRBLinExpr;
import gurobi.GRBModel;
import gurobi.GRBVar;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.Scanner;
import java.util.Set;



public class NewMdpInteractive  extends MdpInteractive{
	private int nkdominateNd;
	private double [][] scoreMinLp;
	private double [][] scoreMaxLp;
	private double fWeight;
	private double sWeight;
	private int randomStrategie = 0;
	public static void main(String[] args) throws GRBException, IOException{
		Scanner input = new Scanner(System.in);
		System.out.println("#State");
		int nState = Integer.parseInt(input.next());
		System.out.println("#Action");
		int nActions = Integer.parseInt(input.next());
		System.out.println("#Reward");
		int nbRewards = Integer.parseInt(input.next());
		System.out.println("Gamma");
		double gamma = Double.parseDouble(input.next());
		System.out.println("Epsilon");
		double epsi = Double.parseDouble(input.next());
		System.out.println("#Seed of Random");
		rnd = new Random(Integer.parseInt(input.next()));
		System.out.println("Strategie");
		int randomStrategie = Integer.parseInt(input.next());
		//if (randomStrategie == 0){
		System.out.println("First Weight");
		double fWeight = Double.parseDouble(input.next());
		System.out.println("Second Weight");
		double sWeight = Double.parseDouble(input.next());
		//}
		int [] policy = new int [nState];
		int nTest = 100;
		BufferedWriter buffer = null;
		try {
			buffer = new BufferedWriter(new FileWriter("resultNewIvi.csv"));
        } catch (IOException e1) {
			e1.printStackTrace();
		}
		for (int t = 1; t <= nTest ; t ++) {
		NewMdpInteractive mdpNewIvi = new NewMdpInteractive(nState, nActions, gamma, epsi, nbRewards, fWeight, sWeight, randomStrategie);
		System.out.println(mdpNewIvi.toString(false));
		policy = mdpNewIvi.valueIteration();
		StringBuffer chaine =  new StringBuffer();
		chaine.append(mdpNewIvi.showValueStates()+"\n");
		chaine.append("Policy  = (");
		for (int i = 0; i < nState; i++) {
			chaine.append(policy[i]+" ");
		}
		buffer.write("Test " +t+ "; # Query => ;" +mdpNewIvi.getNbQuery()+"; Policy => ;" +mdpNewIvi.showPolicy(policy));
		buffer.newLine();
		buffer.flush();
		chaine.append(") \n");
		chaine.append("#Iteration = "+ mdpNewIvi.getNbIteration()+ "\n");
		chaine.append("#Query = " +mdpNewIvi.getNbQuery()+"\n");
		chaine.append("#Kdominate Succeeded = " +mdpNewIvi.getnbkd()+"\n");
		chaine.append("#ParetoDominate Succeeded = "+mdpNewIvi.getnbpD()+"\n");
		chaine.append("#KdominateND succeeded = " +mdpNewIvi.getND()+"\n");
		System.out.println(chaine.toString());
		chaine.setLength(0);
		mdpNewIvi = null;
		}
	}

	public NewMdpInteractive(int nState, int nAction, double gamma, double epsilon, Vector scale, double [][][] transition, double [][] reward, double f, double s, int r){
		super(nState, nAction, gamma, epsilon, scale, transition, reward);
		this.scoreMinLp = new double [nAction][nAction];
		this.scoreMaxLp = new double [nAction][nAction];
		this.fWeight = f;
		this.sWeight = s;
		this.randomStrategie = r;
	}

	public NewMdpInteractive(int nState, int nAction, double gamma, double epsilon, int nbReward, double f, double s, int r){
		super(nState, nAction, gamma, epsilon, nbReward);
		this.scoreMinLp = new double [nAction][nAction];
		this.scoreMaxLp = new double [nAction][nAction];
		this.fWeight = f;
		this.sWeight = s;
		this.randomStrategie = r;
	}

	public int [] valueIteration() throws GRBException{
		int t = 0;
		ArrayList<double []> k = this.init();
		Vector vTempstates = new Vector(this.nReward); 
		int [] policy = new int [nState];
		Vector actions = new Vector(nState);
		boolean result = false;
		boolean stop = false;
		do {
			t = t+1;
			for (int i = 0; i < this.nState; i ++) {
				HashMap<Integer, Vector> nd = new HashMap<Integer, Vector>();
				Vector best = new Vector(this.nReward);
				int bestAction = Integer.MIN_VALUE;
				for (int j = 0; j < this.nAction; j ++) {
					Vector qValueTempStates = new Vector(this.nReward);
					for (int w = 0; w < this.nReward; w ++) {
						double tmps = 0.0;
						for (int e = 0; e < this.nState; e ++) {
							tmps += transition[i][j][e] * valueOldState.getValue(e,w);
						}
						qValue[i][j][w] = reward[i][j][w] + gamma * tmps; 
						qValueTempStates.add(w, qValue[i][j][w]);
					}
					getBestWithoutQueryND(nd, j, qValueTempStates, k); 
				}
				if (nd.size() == 1) {
					bestAction = (int) nd.keySet().toArray()[0];
					best = nd.get(nd.keySet().toArray()[0]);
				}
				if ( nd.size()> 1) {
					best = getBestND(nd, k);
					bestAction = (int) nd.keySet().toArray()[0]; // on sait qu'il n'y a qu'un seul element.
				}
				vNew.updateMatrix(best, i);
				actions.add(i, (double) bestAction);
			}
			vTempstates=computeDistance(vNew, valueOldState);
			result = testStop(vTempstates, epsilon);
			if (result) {
				stop = true;
				this.nbrIteration=t;
			} else {
				valueOldState.updateValueOldStates(vNew);
			}
			if (t%10 == 0) System.out.println("Step = "+t+"\t max Error = "+vTempstates.getMaxValue());
		} while (!stop);
		for ( int i = 0; i < this.nState; i++) {
			policy[i] =  actions.get(i).intValue();
		}
		return policy;
	}

	public void initScore(){
		for (int i = 0; i < nAction; i ++){
			for (int j = 0; j < nAction; j++) {
				scoreMinLp[i][j] = 0.0;
				scoreMaxLp[i][j] = 0.0;
			}
		}
	}

	public Vector getBestND(HashMap<Integer, Vector> nd, ArrayList<double[]> k) throws GRBException{
		double [] score = new double [2];
		int []  indice = new int [2];
		Random randomItem = new Random();
		while (nd.size() != 1) {
			double bestScore = Double.NEGATIVE_INFINITY;
			if (randomStrategie == 1) {
				//System.out.println(nd.size()+" "+indice[0] +" "+ indice[1]);
				do{
			 indice[0] = (int) nd.keySet().toArray()[randomItem.nextInt(nd.size())];
			 indice[1] = (int) nd.keySet().toArray()[randomItem.nextInt(nd.size())];
			// System.out.println(nd.size()+" "+indice[0] +" "+ indice[1]);
			 } while (indice[0] == indice[1]);
			} else {
			for (Map.Entry<Integer, Vector> entry1 : nd.entrySet()) {
				int key1 = entry1.getKey();
				for ( Map.Entry<Integer, Vector> entry2 : nd.entrySet()) {
					int key2 = entry2.getKey();
					if (key1 != key2) {
						score[0] = scoreMinLp[key1][key2];
						score[1] = scoreMaxLp[key1][key2]; 
						double tmp = owa(score[0], score[1], fWeight, sWeight);
						if (bestScore < tmp) {
							indice[0] = key1;
							indice[1] = key2;
							bestScore = tmp;
						}
					}
				}
			  }
			}
			Vector v1 = nd.get((int)indice[0]);
			Vector v2 = nd.get((int)indice[1]);
			boolean rQuery = query(v1, v2);
			if (rQuery) {
				nd.remove((int) indice[1]);
				this.updateK(v1, v2, k);
			} else {
				nd.remove((int) indice[0]);
				this.updateK(v2, v1, k);
			}
			if (nd.size() != 1) kDominateND(nd, k);
		} 
		return nd.get(nd.keySet().toArray()[0]); // On sait qu'il n'y a qu'un seul élément.
	}

	public void kDominateND(HashMap<Integer, Vector> nd, ArrayList<double[]> k) throws GRBException{
		ArrayList<Integer> list = new ArrayList<Integer>(); 
		for (Map.Entry<Integer, Vector> entry1 : nd.entrySet()) {
			int key1 = entry1.getKey();
			if (list.contains(key1)) continue;
			for (Map.Entry<Integer, Vector> entry2 : nd.entrySet()) {
				int key2 = entry2.getKey();
				if (list.contains(key2)) continue;
				boolean result = false;
				if (key1 != key2) {
					result = kDominate(entry1.getValue(), entry2.getValue(), k, this.eta, 1, scoreMinLp, scoreMaxLp, key1, key2);
					if (result) {
						list.add(key2);
						this.nkdominateNd++;
					} else {
						result = kDominate(entry1.getValue(), entry2.getValue(), k, this.eta, -1, scoreMinLp, scoreMaxLp, key1, key2);
						if (result) {
							list.add(key1);
							this.nkdominateNd++;
						} 
					}
				}
			}
		}
		for ( int e = 0; e < list.size(); e ++) {
			nd.remove(list.get(e));
		}
	}

	public double  owa(double fScore, double sScore, double fWeight, double sWeight){
		if (fScore < sScore){
			return ((fWeight * fScore) + (sWeight * sScore));
		} else { 
			return ((fWeight * sScore) + (sWeight * fScore));
		}
	}
	
	public int getBestWithoutQueryND(HashMap<Integer, Vector> nd, int j, Vector v, ArrayList<double[]> k) throws GRBException{
		if (nd.isEmpty()) {
			nd.put(j,v);
		} else {		
			Iterator<Integer> i = nd.keySet().iterator();
			ArrayList<Integer> list = new ArrayList<Integer>();
			while (i.hasNext()) {
				int key = i.next();
				int vectorTmp = getBestWithoutQuery(v, nd.get(key), k, j, key);
				if (vectorTmp == -1) {
					return 0;
				}
				if (vectorTmp == 1) {
					list.add(key);
				}
			}
			for (int e = 0; e < list.size();e ++){
				nd.remove(list.get(e));
			}
			nd.put(j,v);
		}
		return 1;
	}

	public int getBestWithoutQuery(Vector v1, Vector v2, ArrayList<double[]> k, int fAction, int sAction) throws GRBException{
		boolean result = false;
		result =  v1.compare(v2);
		if (result) { 
			this.nbrParetoDominate++;
			return 1;
		} else {  
			result = v2.compare(v1);
			if (result) {
				this.nbrParetoDominate++;
				return -1;
			} else {
				result = kDominate(v1, v2, k, eta, 1, scoreMinLp, scoreMaxLp, fAction, sAction);
				if (result) {
					return 1;
				} else {
					result = kDominate(v1, v2, k, eta, -1, scoreMinLp, scoreMaxLp, fAction, sAction);
					if (result) {
						return -1;
					}
				}
			}
		}
		return 0;
	}
	
	public boolean kDominate(Vector v1, Vector v2, ArrayList<double []> k, double eta, int sens, double [][] scoreMin, double [][] scoreMax, int fAction, int sAction) throws GRBException{
		GRBEnv env = new GRBEnv(); 
		env.set(GRB.IntParam.LogToConsole, 0);
		env.set(GRB.IntParam.OutputFlag, 0);
		env.set(GRB.StringParam.LogFile, "");
		GRBModel model = new GRBModel(env);
		model.set(GRB.StringAttr.ModelName, "LP.IVI");
		GRBVar[] varDecision = new GRBVar[nReward];  //les variables de decisions
		for (int i = 0; i < nReward; i ++) {
			varDecision[i] = model.addVar(0, this.rewardSet.get(this.nReward-1),  v1.get(i) - v2.get(i), GRB.CONTINUOUS, "reward"+i);
		}
		model.set(GRB.IntAttr.ModelSense, sens);
		model.update();		
		for (int p = 0; p < k.size(); p ++) {   
			GRBLinExpr leftHandSide = new GRBLinExpr();   
			for (int w = 0; w < k.get(0).length; w ++) {   
				leftHandSide.addTerm(k.get(p)[w], varDecision[w]);
			}
			if (p < this.nReward-1) {
				model.addConstr(leftHandSide, GRB.GREATER_EQUAL, eta, "constraint"+p);
			} else {
				model.addConstr(leftHandSide, GRB.GREATER_EQUAL, 0, "constraint"+p);
			}
		}
		model.getEnv().set(GRB.IntParam.Method , GRB.METHOD_CONCURRENT);
		model.optimize();
		double objectiveValue = model.get(GRB.DoubleAttr.ObjVal);
		model.dispose();
		env.dispose();
		if (sens == 1 && objectiveValue >= 0) { 
			this.nbrKdominate++;
			scoreMin[fAction][sAction] = Math.abs(objectiveValue);
			return true;
		}
		if (sens == -1 && objectiveValue <= 0) {
			this.nbrKdominate++;
			scoreMax[fAction][sAction] = Math.abs(objectiveValue);
			return true;
		}
		return false;
	}
	public int getND(){
		return this.nkdominateNd;
	}
	public int getRandomStrategie(){
		return this.randomStrategie;
	}
}
